/**
    @file pixellayer.cpp
    @brief Game map layer
*/
#include "pixel/pixellayer.hpp"
#include "lua/lua_pixellayer.hpp"

pm3::PIXELLAYER *pm3::lua::g_active_layer = NULL;

pm3::PIXELLAYER::PIXELLAYER() {
    m_id = -1;

    m_image_unsaved = false;

    m_render_mode = pm3::RM_PVS;
    m_show_debug = false;

    m_offset_x = 0;
    m_offset_y = 0;
}

pm3::PIXELLAYER::~PIXELLAYER() {
    unload();
}

pm3::PIXELLAYER& pm3::PIXELLAYER::operator=( const PIXELLAYER &r_layer ) {
    m_id = r_layer.m_id;
    m_rooms = r_layer.m_rooms;
    m_image = r_layer.m_image;
    m_image_path = r_layer.m_image_path;

    m_offset_x = r_layer.m_offset_x;
    m_offset_y = r_layer.m_offset_y;

    m_render_mode = r_layer.m_render_mode;
    m_show_debug = r_layer.m_show_debug;
    m_image_unsaved = r_layer.m_image_unsaved;

    return *this;
}

void pm3::IMAGE::setColourAt(int x, int y, int z, const Ogre::ColourValue &r_color) {
    Ogre::PixelUtil::packColour( r_color, m_eFormat, &m_pBuffer[ m_ucPixelSize * (z * m_uWidth * m_uHeight + m_uWidth * y + x ) ] );
}

void pm3::IMAGE::resize(int x, int y, int width, int height) {
    Ogre::ColourValue col;
    int i, j;

    try {
        // Allocate a new buffer
        m_uSize = m_ucPixelSize * width * height;
        unsigned char *p_new_buf = OGRE_ALLOC_T( unsigned char, m_uSize, Ogre::MEMCATEGORY_GENERAL);

        // Start copying
        for (j=0; j<height; j++) {
            for (i=0; i<width; i++) {
                // Are we within the region of the old image?
                if ( i >= x && i < x + m_uWidth &&
                     j >= y && j < y + m_uHeight ) {
                    // Copy the pixels
                    col = getColourAt( i - x, j - y, 0 );
                } else {
                    // If not, then just draw black pixels
                    col = Ogre::ColourValue::Black;
                }

                Ogre::PixelUtil::packColour( col, m_eFormat, &p_new_buf[ m_ucPixelSize * ( width * j + i ) ] );
            }
        }

        // Update image dimensions
        m_uWidth = width;
        m_uHeight = height;

        // Free the old buffer
        freeMemory();
        // And replace it with the new one
        m_pBuffer = p_new_buf;

    } catch( ... ) {
        // Shouldn't happen
    }
}

void pm3::PIXELLAYER::unload() {
    int width = get_width();

    m_image_path = "";

    m_id = -1;

    m_rooms.clear();

    m_offset_x = 0;
    m_offset_y = 0;
}

pm3::PIXELROOM *pm3::PIXELLAYER::get_room( int i_x, int i_y ) {
    std::list< std::list< PIXELROOM > >::iterator it;
    std::list< PIXELROOM >::iterator it2;

    int width = get_width();
    int height = get_height();
    int x = 0, y = 0;

    if (i_x + m_offset_x >= 0 && i_x + m_offset_x < width && 
        i_y + m_offset_y >= 0 && i_y + m_offset_y < height) {

        it = m_rooms.begin();
        while( it != m_rooms.end() ) {
            x = 0;
            it2 = it->begin();
            while( it2 != it->end() ) {
                // Found it?
                if (x == i_x + m_offset_x && y == i_y + m_offset_y)
                    return &(*it2);

                it2++;
                x++;
            }

            it++;
            y++;
        }
    }

    return NULL;
}

void pm3::PIXELLAYER::add_columns_left( int i_num ) {
    std::list< std::list< PIXELROOM > >::iterator it;
    PIXELROOM room;
    int j = 0;
    // Leftmost X-coordinate
    int x0 = m_rooms.front().front().get_grid_pos().x;
    // Topmost Y-coordinate
    int y0 = m_rooms.front().front().get_grid_pos().z;

    // Add columns of rooms
    it = m_rooms.begin();
    while( it != m_rooms.end() ) {
        // Add i_num rooms to the left end of the row
        for (int i=1; i<=i_num; i++) {
            // Add a room
            it->push_front( room );
            // Set its position
            it->front().set_grid_pos( Ogre::Vector3( x0 - i, m_id, y0 + j ) );
        }
        // Next row
        j++;
        it++;
    }

    // Shift the offset
    m_offset_x += i_num;
}

void pm3::PIXELLAYER::add_columns_right( int i_num ) {
    std::list< std::list< PIXELROOM > >::iterator it;
    int width = m_rooms.front().size();
    PIXELROOM room;
    // Rightmost X-coordinate
    int x1 = m_rooms.front().back().get_grid_pos().x;
    // Topmost Y-coordinate
    int y0 = m_rooms.front().front().get_grid_pos().z;
    int j = 0;

    // Add columns of rooms
    it = m_rooms.begin();
    while( it != m_rooms.end() ) {
        // Add i_num rooms at the end of the row
        for (int i=1; i<=i_num; i++) {
            // Add room
            it->push_back( room );
            // Set its position
            it->back().set_grid_pos( Ogre::Vector3( x1 + i, m_id, y0 + j ) );
        }
        // Next row
        j++;
        it++;
    }
}

void pm3::PIXELLAYER::add_rows_top( int i_num ) {
    std::list< PIXELROOM > empty_row;
    int width = m_rooms.front().size();
    PIXELROOM room;
    // Leftmost X-coordinate
    int x0 = m_rooms.front().front().get_grid_pos().x;
    // Topmost Y-coordinate
    int y0 = m_rooms.front().front().get_grid_pos().z;

    // Add rows of rooms
    for (int i=1; i<=i_num; i++) {
        m_rooms.push_front( empty_row );

        for (int j=x0; j<width; j++) {
            // Push empty room
            m_rooms.front().push_back( room );
            // Set room position
            m_rooms.front().back().set_grid_pos( Ogre::Vector3( j, m_id, y0 - i ) );
        }
    }

    // Shift the offset
    m_offset_y += i_num;
}

void pm3::PIXELLAYER::add_rows_bottom( int i_num ) {
    std::list< PIXELROOM > empty_row;
    int width = m_rooms.front().size();
    // Leftmost X-coordinate
    int x0 = m_rooms.front().front().get_grid_pos().x;
    // Bottom-most Y-coordinate
    int y1 = m_rooms.back().front().get_grid_pos().z;
    PIXELROOM room;

    // Add rows of rooms
    for (int i=1; i<=i_num; i++) {
        m_rooms.push_back( empty_row );

        for (int j=0; j<width; j++) {
            // Push empty room
            m_rooms.back().push_back( room );
            // Set room position
            m_rooms.back().back().set_grid_pos( Ogre::Vector3( x0 + j, m_id, y1 + i ) );
        }
    }
}

Ogre::ColourValue pm3::PIXELLAYER::get_pixel( int i_x, int i_y ) {
    Ogre::ColourValue color;
    int width = get_width();
    int height = get_height();

    if (i_x + m_offset_x >= 0 && i_x + m_offset_x < width && 
        i_y + m_offset_y >= 0 && i_y + m_offset_y < height)
        color = m_image.getColourAt( i_x + m_offset_x, i_y + m_offset_y, 0 );

    return color;
}

void pm3::PIXELLAYER::set_pixel( int i_x, int i_y, const Ogre::ColourValue &r_color ) {
    int width = get_width();
    int height = get_height();
    int dx = 0, dy = 0;

    if (i_x + m_offset_x >= 0 && i_x + m_offset_x < width && 
        i_y + m_offset_y >= 0 && i_y + m_offset_y < height) {
        m_image.setColourAt( i_x + m_offset_x, i_y + m_offset_y, 0, r_color );

        // Mark the layer image as unsaved
        m_image_unsaved = true;
    } else {
        dx = i_x + m_offset_x;
        dy = i_y + m_offset_y;

        // Negative x?
        if (dx < 0) {
            dx = -dx;
            add_columns_left( dx );
            // Image grows wider by dx
            width += dx;
        // Too great x?
        } else if (dx >= width) {
            // Note that the maximum index is width-1 (which is why +1 is needed)
            add_columns_right( dx - width + 1 );
            // Image grows as wide as dx (see the comment above)
            width = dx + 1;
            dx = 0;
        } else
            dx = 0;

        // Negative y?
        if (dy < 0) {
            dy = -dy;
            add_rows_top( dy );
            // Image grows taller by dy
            height += dy;
        // Too great y?
        } else if (dy >= height) {
            // Note that the maximum index is height-1 (which is why +1 is needed)
            add_rows_bottom( dy - height + 1 );
            // Image grows as tall as dy (see the comment above)
            height = dy + 1;
            dy = 0;
        } else
            dy = 0;

        // Resize image
        // Note tha image should only be offset when setting pixels with negative coordinates
        m_image.resize( dx, dy, width, height );

        // Set pixel
        m_image.setColourAt( i_x + m_offset_x, i_y + m_offset_y, 0, r_color );

        // Image probably has unsaved changes
        m_image_unsaved = true;
        // Script probably has unsaved changes as well
        m_unsaved = true;
    }
}

void pm3::PIXELLAYER::init( int i_id ) {
    std::list< PIXELROOM > empty_row;
    PIXELROOM room;

    int width = get_width();
    int height = get_height();

    m_id = i_id;

    // Allocate memory for the rooms
    if (width > 0 && height > 0) {
        for (int y=0; y<height; y++) {
            // Push an empty row
            m_rooms.push_back( empty_row );
            // And fill it
            for (int x=0; x<width; x++) {
                // Push empty room
                m_rooms.back().push_back( room );
                // Set room position
                m_rooms.back().back().set_grid_pos( Ogre::Vector3( x - m_offset_x, i_id, y - m_offset_y ) );
            }
        }
    }
}

void pm3::PIXELLAYER::bind() {
    tolua_layerscript_open( m_lua_state );
}

void pm3::PIXELLAYER::update() {
    // Set the active layer
    pm3::lua::g_active_layer = this;
    // Proceed with the default luascript update
    pm3::LUA_SCRIPTED::update();
}

bool pm3::PIXELLAYER::load_image( const std::string &r_path, const std::string &r_group ) {
    LOGGER *log = LOGGER::getSingletonPtr();
    std::string str;

    str = get_directory() + r_path;

    // Attempt to load the image
    try {
        // Store image path (relative)
        m_image_path = r_path;
        // Load the image
        m_image.load( str, r_group );

        // Mark the layer image as saved
        m_image_unsaved = false;
        // Mark the layer script as unsaved
        m_unsaved = true;

        if (log)
            log->report( "Pixel", "I: Loaded image \"%s\" in resource group \"%s\"\n", str.c_str(), r_group.c_str() );

        return true;
    } catch( Ogre::FileNotFoundException ex ) {
        if (log)
            log->report( "Pixel", "E: Failed to load image \"%s\" in resource group \"%s\"\n", str.c_str(), r_group.c_str() );
    }
    return false;
}

std::string pm3::PIXELLAYER::save_image( const std::string &r_path ) {
    if (r_path.empty())
        return "";

    std::string path = get_directory() + r_path;

    m_image.save( path );

    // Mark the layer image as saved
    m_image_unsaved = false;

    return path;
}

std::string pm3::PIXELLAYER::save_script( const std::string &r_path ) {
    if (r_path.empty())
        return "";

    FILE *fo = fopen( r_path.c_str(), "wt" );

    if (fo) {
        fprintf( fo, "-- PMaz3 layerscript\n" );
        fprintf( fo, "layer = get_active_layer();\n\n" );

        fprintf( fo, "layer:load_image( \"%s\", \"General\" );\n", m_image_path.c_str() );

        // Save coordinate offsets
        if (m_offset_x != 0 || m_offset_y != 0) {
            fprintf( fo, "-- Save coordinate offsets for backwards compatibility after resizing layer image\n" );
            fprintf( fo, "layer:set_coord_offset( %d, %d );\n", m_offset_x, m_offset_y );
        }

        // Mark the layer script as saved
        m_unsaved = false;

        fclose( fo );

        return r_path;
    }

    return "";
}

std::string pm3::PIXELLAYER::get_room_var( int i_x, int i_y, const std::string &r_name ) {
    LOGGER *log = LOGGER::getSingletonPtr();
    PIXELROOM *room = NULL;

    // Get the room
    room = get_room( i_x, i_y );
    // Was there such a room?
    if (room)
        return room->get_variable( r_name );
    // Nope?
    return "";
}

bool pm3::PIXELLAYER::set_room_var( int i_x, int i_y, const std::string &r_name, const std::string &r_val ) {
    LOGGER *log = LOGGER::getSingletonPtr();
    PIXELROOM *room = NULL;

    // Get the room
    room = get_room( i_x, i_y );
    // Was there such a room?
    if (room) {
        room->set_variable( r_name, r_val );

        // Mark the layer script as unsaved
        m_unsaved = true;

        return true;
    }
    // Nope?
    return false;
}

void pm3::PIXELLAYER::set_render_mode( pm3::RENDER_MODE e_mode ) {
    m_render_mode = e_mode;

    std::list< std::list< PIXELROOM > >::iterator it;
    std::list< PIXELROOM >::iterator it2;

    it = m_rooms.begin();
    while( it != m_rooms.end() ) {
        it2 = it->begin();
        while( it2 != it->end() ) {
            // Set rendering mode
            it2->set_render_mode( e_mode );

            it2++;
        }

        it++;
    }
}

void pm3::PIXELLAYER::show_debug_boxes( bool b_show ) {
    m_show_debug = b_show;

    std::list< std::list< PIXELROOM > >::iterator it;
    std::list< PIXELROOM >::iterator it2;

    it = m_rooms.begin();
    while( it != m_rooms.end() ) {
        it2 = it->begin();
        while( it2 != it->end() ) {
            // Set debug render mode
            it2->show_debug_box( b_show );

            it2++;
        }

        it++;
    }
}

void pm3::PIXELLAYER::set_coord_offset( int i_x, int i_y ) {
    // Update layer coordinate offsets
    if (i_x >= 0 && i_x < get_width() &&
        i_y >= 0 && i_y < get_height()) {
        m_offset_x = i_x;
        m_offset_y = i_y;
    }

    std::list< std::list< PIXELROOM > >::iterator it;
    std::list< PIXELROOM >::iterator it2;
    int x = 0, y = 0;

    it = m_rooms.begin();
    while( it != m_rooms.end() ) {
        x = 0;
        it2 = it->begin();
        while( it2 != it->end() ) {
            // Update grid positions
            it2->set_grid_pos( Ogre::Vector3( x - m_offset_x, m_id, y - m_offset_y ) );

            it2++;
            x++;
        }

        it++;
        y++;
    }
}

